/*=====================================================================================
* Copyright (c) 2020, micROS Group, NIIDT, TAIIC.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
*  provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and
*      the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
*      and the following disclaimer in the documentation and/or other materials provided with the
*      distribution.
* 3. All advertising materials mentioning features or use of this software must display the following
*      acknowledgement: This product includes software developed by the micROS Group and its
*      contributors.
* 4. Neither the name of the Group nor the names of contributors may be used to endorse or promote
*     products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY MICROS GROUP AND CONTRIBUTORS ''AS IS''AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR  PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE MICROS, GROUP OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
*  SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
*  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION). HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
=======================================================================================
*/

#include "yolov3_clip_detector.h"
#include <algorithm>
#include "acl/acl.h"
#include <dirent.h>
#include <math.h>
#include <cstdlib>
#include <chrono>
#include <unistd.h>


void DestroyResource()
{
    aclError ret = aclFinalize();
    if (ret != ACL_SUCCESS) {
      std::cout<<"execute  aclFinalize false"<<std::endl;
    }
}

Yolov3CLIPDetector::Yolov3CLIPDetector()
{
  std::cout<<"-- Creat instence of Resource Yolov3CLIPDetector"<<std::endl;
}

Yolov3CLIPDetector::~Yolov3CLIPDetector()
{
  aclmdlDestroyDesc(modelDesc);
  aclmdlUnload(modelId);
  
  aclrtFree(pictureDeviceData);
  pictureDeviceData = nullptr;
  aclDestroyDataBuffer(inputBuffer);
  inputBuffer = nullptr;
  aclmdlDestroyDataset(input);
  input = nullptr;

  aclDestroyDataBuffer(outputData);
  outputData = nullptr;
  aclrtFree(outputDeviceData);
  outputDeviceData = nullptr;
  aclmdlDestroyDataset(output);
  output = nullptr;
  aclError ret = aclrtResetDevice(deviceId);
  DestroyResource();
  DestroyInput();
}

void Yolov3CLIPDetector::DestroyInput()
{
    if (input == nullptr) {
        return;
    }

    aclrtFree(pictureDeviceData);
    pictureDeviceData = nullptr;
    aclDestroyDataBuffer(inputBuffer);
    inputBuffer = nullptr;
    aclmdlDestroyDataset(input);
    input = nullptr;

    std::cout<<"destroy model input success"<<std::endl;
}

void Yolov3CLIPDetector::DestroyOutput()
{
    if (output == nullptr) {
        return;
    }

    for (size_t i = 0; i < aclmdlGetDatasetNumBuffers(output); ++i) {
        aclDataBuffer *dataBuffer = aclmdlGetDatasetBuffer(output, i);
        void *data = aclGetDataBufferAddr(dataBuffer);
        (void)aclrtFree(data);
        (void)aclDestroyDataBuffer(dataBuffer);
    }

    (void)aclmdlDestroyDataset(output);
    output = nullptr;
    std::cout<<"destroy model output success"<<std::endl;
}


  bool Yolov3CLIPDetector::initEngine(const char * modelPath){
    aclError ret = aclInit(nullptr);
    if (ret != ACL_SUCCESS) {
        std::cout<<"acl init false, errorCode = "<< static_cast<int32_t>(ret)<<std::endl;
        return false;
    }
    std::cout<<"acl init success"<<std::endl;

    ret = aclrtSetDevice(deviceId);
    if (ret != ACL_SUCCESS) {
        std::cout<<"acl set device "<<deviceId<<" false, errorCode = "<< static_cast<int32_t>(ret)<<std::endl;
        return false;
    }
    std::cout<<"set device "<<deviceId<<" success"<<std::endl;

    // get run mode
    ret = aclrtGetRunMode(&runMode);
    if (ret != ACL_SUCCESS) {
        std::cout<<"acl get run mode false, errorCode = "<<static_cast<int32_t>(ret)<<std::endl;
        return false;
    }
    std::cout<<"get run mode success"<<std::endl;

    // model init
    ret = aclmdlLoadFromFile(modelPath, &modelId);
    if (ret != ACL_SUCCESS) {
        std::cout<<"execute LoadModel false"<<std::endl;
        return false;
    }

    modelDesc = aclmdlCreateDesc();
    if (modelDesc == nullptr) {
        std::cout<<"create model description failed"<<std::endl;
        return false;
    }

    ret = aclmdlGetDesc(modelDesc, modelId);
    if (ret != ACL_SUCCESS) {
        std::cout<<"get model description failed, modelId is "<<modelId<< ", errorCode is "<<static_cast<int32_t>(ret)<<std::endl;
        return false;
    }

    std::cout<<"create model description success"<<std::endl;

  

    // cteate output
    output = aclmdlCreateDataset();
    if (output == nullptr) {
        std::cout<<"can't create dataset, create output false"<<std::endl;
        return false;
    }
    modelOutputSize = aclmdlGetOutputSizeByIndex(modelDesc, 0);
    std::cout<<"output size: "<<modelOutputSize<<std::endl;
    ret = aclrtMalloc(&outputDeviceData, modelOutputSize, ACL_MEM_MALLOC_NORMAL_ONLY);
    if (ret != ACL_SUCCESS) {
      std::cout<<"can't malloc buffer, size is"<< modelOutputSize<<"create output false, errorCode is "<<static_cast<int32_t>(ret)<<std::endl;
        return false;
    }

    outputData = aclCreateDataBuffer(outputDeviceData, modelOutputSize);
    if (outputData == nullptr) {
        std::cout<<"can't create data buffer, create output false"<<std::endl;
        (void)aclrtFree(outputDeviceData);
        return false;
    }

    ret = aclmdlAddDatasetBuffer(output, outputData);
    if (ret != ACL_SUCCESS) {
        std::cout<<"can't add data buffer, create output false, errorCode is "<<static_cast<int32_t>(ret)<<std::endl;
        (void)aclrtFree(outputDeviceData);
        (void)aclDestroyDataBuffer(outputData);
        return false;
    }
    std::cout<<"create model output success"<<std::endl;
    fileSize = batchsize * 224 * 224 * 3;
    if (pictureDeviceData == nullptr)
    {
        ret = aclrtMalloc(&pictureDeviceData, fileSize * 4, ACL_MEM_MALLOC_HUGE_FIRST);
    }
    
   
    input = aclmdlCreateDataset();
    for(size_t index = 0; index < aclmdlGetNumInputs(modelDesc); ++index)
    {
        const char* name = aclmdlGetInputNameByIndex(modelDesc, index);
        size_t inputLen = aclmdlGetInputSizeByIndex(modelDesc, index);
	      std::cout<<"input name: "<<name<<", length: "<<inputLen<<std::endl;
        if(strcmp(name, ACL_DYNAMIC_TENSOR_NAME) == 0)
        {
            void *data = nullptr;
            ret = aclrtMalloc(&data, inputLen, ACL_MEM_MALLOC_HUGE_FIRST);
            batchBuffer = aclCreateDataBuffer(data, inputLen);
            ret = aclmdlAddDatasetBuffer(input, batchBuffer);
        }else{
            inputBuffer = aclCreateDataBuffer(pictureDeviceData, fileSize * 4);
            ret = aclmdlAddDatasetBuffer(input, inputBuffer);
        }
    }

    size_t index;
    ret = aclmdlGetInputIndexByName(modelDesc, ACL_DYNAMIC_TENSOR_NAME,&index);
    if (ret != ACL_SUCCESS) {
        std::cout<<"execute aclmdlGetInputIndexByName false"<<std::endl;
        return false;
    }
    ret = aclmdlSetDynamicBatchSize(modelId, input, index, batchsize);
    if (ret != ACL_SUCCESS) {
        std::cout<<"execute aclmdlSetDynamicBatchSize false"<<std::endl;
        return false;
    }
    return true;
  }

 /**
 * @brief The interface class of init
 * @param resource_id
 * @return true or false
 * 
 */  
  bool Yolov3CLIPDetector::Initialize(const char * modelPath, size_t batch_size)
  {
    batchsize = batch_size;
    aclError ret;
    bool flag = initEngine(modelPath);
    if (!flag){
      std::cout<<"init false"<<std::endl;
      return false;
    }
    
    return true;
  }


  bool Yolov3CLIPDetector::inference(std::vector<cv::Mat> &imgs)
  {
    aclError ret;
    //size_t batchsize = imgs.size();
    //uint32_t fileSize = batchsize * 224 * 224 * 3;
    std::cout<<"input size: "<<fileSize<<std::endl;
    std::vector<float>input_data;
    for (size_t i= 0; i< batchsize; i++){
      cv::Mat img = imgs[i];
      image_preprocess(img, input_data);
    }
  
    if (runMode == ACL_HOST) {
        ret = aclrtMemcpy((char *)pictureDeviceData, fileSize * 4, \
        input_data.data(), fileSize * 4 , ACL_MEMCPY_HOST_TO_DEVICE);
    }
    else
    {
        ret = aclrtMemcpy((char *)pictureDeviceData, fileSize * 4, \
        input_data.data(), fileSize * 4, ACL_MEMCPY_DEVICE_TO_DEVICE);
    }
    ret = aclmdlExecute(modelId, input, output);
    if (ret != ACL_SUCCESS) {
        std::cout<<"execute aclmdlExecute false "<<static_cast<int32_t>(ret)<<std::endl;
        return false;
    }
    
    float* outFloatData;
    if (runMode == ACL_HOST) {
        aclError ret = aclrtMallocHost(&outputHostData, modelOutputSize);
        ret = aclrtMemcpy(outputHostData, modelOutputSize, outputDeviceData, modelOutputSize, ACL_MEMCPY_DEVICE_TO_HOST);
        outFloatData = reinterpret_cast < float * > (outputHostData);
    }
    else
    {
        outFloatData = reinterpret_cast < float * > (outputDeviceData);
    }
    for (int i = 0; i< batchsize * 6; i++){
      std::cout<<"i: "<<i<<", val: "<<*outFloatData<<std::endl;
      outFloatData++;
    }

    return true;

}
  

  void Yolov3CLIPDetector::image_preprocess(cv::Mat &frame, std::vector<float>&input_data) {
      cv::Mat reiszedImage, rsImageF32, dst_image;
      cv::resize(frame, reiszedImage, cv::Size(clipWidth, clipHeight),0, 0,cv::INTER_CUBIC);
      cv::Mat shipRGB;
      cv::cvtColor(reiszedImage, shipRGB, cv::COLOR_BGR2RGB);
      shipRGB.convertTo(rsImageF32, CV_32FC3);
      rsImageF32.copyTo(dst_image);

      for (int i=0; i<rsImageF32.rows; i++){
          for (int j=0; j<rsImageF32.cols; j++){
              cv::Vec3f channel_data =  rsImageF32.at<cv::Vec3f>(i, j);
              channel_data[2] = (channel_data[2] / 255.0f - means[2]) / stds[2];
              channel_data[1] = (channel_data[1] / 255.0f - means[1]) / stds[1];
              channel_data[0] = (channel_data[0] / 255.0f - means[0]) / stds[0];
              dst_image.at<cv::Vec3f>(i, j) = channel_data;
          }
      }
  
      cv::Mat channels[3];
      cv::split(dst_image, channels);
      for (int i=0; i<dst_image.channels(); i++){
          std::vector<float> data = std::vector<float>(channels[i].reshape(1, dst_image.cols * dst_image.rows));
          input_data.insert(input_data.end(), data.begin(), data.end());
        }
  }

  std::vector<float> Yolov3CLIPDetector::softmax(std::vector<float> &conf, float temperature)
  {
    for (auto &value : conf)
    {
      value *= temperature;
    }

    std::vector<float>::iterator biggest = std::max_element(conf.begin(),conf.end());
    float max_value = *biggest;
    
    float total = 0;
    for (auto x : conf)
    {
      total += exp(x-max_value);
    }

    std::vector<float> results;
    for (auto x : conf)
    {
      results.push_back(exp(x-max_value)/total);
    }
    return results;
  }
  

